use std::{collections::HashMap, ops::Deref, sync::Arc};

use ane_config::Config;
use axum::response::Html;
use axum_client_ip::SecureClientIpSource;
use casbin::{CoreApi, MgmtApi};
use once_cell::sync::OnceCell;
use regex::Regex;
use sea_orm::DbConn;
use tera::Tera;
use tokio::sync::RwLock;
use tower::builder;

use crate::{error::AppResult, service::cache_service::ICacheService, utils::casbin_policies};

fn global_regex_map() -> &'static std::sync::RwLock<HashMap<String, Regex>> {
    static INSTANCE: OnceCell<std::sync::RwLock<HashMap<String, Regex>>> = OnceCell::new();
    INSTANCE.get_or_init(|| {
        let mut m = HashMap::new();
        std::sync::RwLock::new(m)
    })
}

struct InnerState {
    conn: DbConn,
    tera: Tera,
    tera_context: tera::Context,
    cache: Box<dyn ICacheService>,
    enforcer: RwLock<casbin::Enforcer>,
    ip_header: Option<SecureClientIpSource>,
}

impl InnerState {
    fn new(
        conn: DbConn,
        tera: Tera,
        tera_context: tera::Context,
        cache: Box<dyn ICacheService>,
        mut enforcer: casbin::Enforcer,
        ip_header: Option<SecureClientIpSource>,
    ) -> Self {
        enforcer.add_function(
            "methodMatch",
            casbin::function_map::OperatorFunction::Arg2(
                |s1: casbin::rhai::ImmutableString, s2: casbin::rhai::ImmutableString| {
                    s2.as_str().find(s1.as_str()).is_some().into()
                },
            ),
        );
        enforcer.add_function(
            "regexMatch",
            casbin::function_map::OperatorFunction::Arg2(
                |s1: casbin::rhai::ImmutableString, s2: casbin::rhai::ImmutableString| {
                    let map = global_regex_map();
                    if let Some(reg) = map.read().unwrap().get(s2.as_str()) {
                        return reg.is_match(s1.as_str()).into();
                    }
                    let reg = Regex::new(s2.as_str()).unwrap();
                    let res = reg.is_match(s1.as_str());
                    let mut s = map.write().unwrap();
                    s.insert(s2.to_string(), reg);
                    res.into()
                },
            ),
        );
        Self {
            conn,
            tera,
            tera_context,
            cache,
            enforcer: RwLock::new(enforcer),
            ip_header,
        }
    }
}

#[derive(Clone)]
pub struct AppState(Arc<InnerState>);

impl AppState {
    pub fn new(
        conn: DbConn,
        tera: Tera,
        tera_context: tera::Context,
        cache: Box<dyn ICacheService>,
        enforcer: casbin::Enforcer,
        ip_header: Option<SecureClientIpSource>,
    ) -> Self {
        Self(InnerState::new(conn, tera, tera_context, cache, enforcer, ip_header).into())
    }

    /// 从配置文件中实例化AppState
    pub async fn from_config(cnf: &Config) -> Self {
        use fred::prelude::{ClientLike, RedisPool};
        use fred::types::RedisConfig;

        // 初始化数据库
        let conn = sea_orm::Database::connect(cnf.database.get_connect_opt())
            .await
            .unwrap();

        // jinja2 模板
        let (mut tera, tera_context) = cnf.template.get_tera();
        #[cfg(feature = "cms")]
        {
            tera.register_function("url_for", crate::controller::cms::view::url_for);
            tera.register_function("iter_page", crate::controller::cms::view::iter_page);
            tera.register_filter("markdown", crate::controller::cms::view::markdown);
        }
        // casbin
        let enforcer = cnf
            .casbin
            .as_ref()
            .expect("miss casbin config")
            .casbin()
            .await
            .unwrap();

        // cache服务
        let redis_config = cnf
            .redis
            .as_ref()
            .and_then(|f| RedisConfig::from_url(&f.url()).ok());
        let pool = if let Some(c) = redis_config {
            let pool = RedisPool::new(c, None, None, None, 4).unwrap();
            let _ = pool.connect();
            pool.wait_for_connect().await.unwrap();
            Some(pool)
        } else {
            None
        };
        let cache: Box<dyn ICacheService> = if let Some(pool) = pool.clone() {
            Box::new(crate::service::cache_service::RedisCache::new(pool))
        } else {
            Box::new(crate::service::cache_service::MokaCache::new(1024))
        };

        let mut state = Self::new(conn, tera, tera_context, cache, enforcer, None);
        state.reload_policies().await.unwrap();
        state
    }

    /// 返回数据库的借用
    pub fn db(&self) -> &DbConn {
        &self.0.conn
    }

    /// 返回缓存服务
    pub fn cache(&self) -> &Box<dyn ICacheService> {
        &self.0.cache
    }

    /// 生成`jinja`模板
    pub fn render_template(
        &self,
        template_name: &str,
        context: &tera::Context,
    ) -> AppResult<Html<String>> {
        (&self.0.tera)
            .render(template_name, context)
            .map(Html)
            .map_err(Into::into)
    }

    /// 返回一个`jinja2`上下文的`clone`
    pub fn tera_context(&self) -> tera::Context {
        self.0.tera_context.clone()
    }

    /// 验证casbin请求
    pub async fn enforce<ARGS: casbin::EnforceArgs>(
        &self,
        rvals: ARGS,
    ) -> Result<bool, casbin::Error> {
        let s = self.0.enforcer.read().await;
        s.enforce(rvals)
    }

    /// 验证多条casbin请求
    pub async fn enforces<ARGS: casbin::EnforceArgs>(
        &self,
        rvals: Vec<ARGS>,
    ) -> Result<Vec<bool>, casbin::Error> {
        let enforce = self.0.enforcer.read().await;
        let mut result = vec![];
        for rval in rvals {
            result.push(enforce.enforce(rval)?);
        }
        Ok(result)
    }

    /// 重新加载casbin policies
    pub async fn reload_policies(&self) -> Result<(), casbin::Error> {
        let policies = casbin_policies(self.db(), None, None).await.unwrap();
        let mut enforce = self.0.enforcer.write().await;
        global_regex_map().write().unwrap().clear();
        enforce.load_policy().await;
        enforce.add_policies(policies).await;
        Ok(())
    }

    pub fn ip_header(&self) -> Option<&SecureClientIpSource> {
        self.0.ip_header.as_ref()
    }
}
